/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief iOS Platform implementation.
 *//*--------------------------------------------------------------------*/

#include "tcuIOSPlatform.hh"
#include "gluRenderConfig.hpp"
#include "gluFboRenderContext.hpp"

#include "glwInitES20Direct.hpp"
#include "glwInitES30Direct.hpp"


namespace tcu
{
namespace ios
{

// ScreenManager

ScreenManager::ScreenManager (tcuEAGLView* view)
	: m_view(view)
{
}

ScreenManager::~ScreenManager (void)
{
}

CAEAGLLayer* ScreenManager::acquireScreen (void)
{
	if (!m_viewLock.tryLock())
		throw ResourceError("View is already is in use");

	return [m_view getEAGLLayer];
}

void ScreenManager::releaseScreen (CAEAGLLayer* layer)
{
	DE_UNREF(layer);
	m_viewLock.unlock();
}

// ContextFactory

ContextFactory::ContextFactory (ScreenManager* screenManager)
	: glu::ContextFactory	("eagl", "iOS EAGL Context")
	, m_screenManager		(screenManager)
{
}

ContextFactory::~ContextFactory (void)
{
}

glu::RenderContext* ContextFactory::createContext (const glu::RenderConfig& config, const tcu::CommandLine&) const
{
	RawContext* rawContext = new RawContext(config.type);

	try
	{
		if (config.surfaceType == glu::RenderConfig::SURFACETYPE_OFFSCREEN_GENERIC)
			return new glu::FboRenderContext(rawContext, config);
		else if (config.surfaceType == glu::RenderConfig::SURFACETYPE_WINDOW)
			return new ScreenContext(m_screenManager, config);
		else
			throw NotSupportedError("Unsupported surface type");
	}
	catch (...)
	{
		delete rawContext;
		throw;
	}
}

// Platform

Platform::Platform (ScreenManager* screenManager)
{
	m_contextFactoryRegistry.registerFactory(new ContextFactory(screenManager));
}

Platform::~Platform (void)
{
}

// RawContext

static EAGLRenderingAPI getEAGLApi (glu::ContextType type)
{
	if (type.getAPI() == glu::ApiType::es(3,0))
		return kEAGLRenderingAPIOpenGLES3;
	else if (type.getAPI() == glu::ApiType::es(2,0))
		return kEAGLRenderingAPIOpenGLES2;
	else
		throw NotSupportedError("Requested GL API is not supported on iOS");
}

RawContext::RawContext (glu::ContextType type)
	: m_type		(type)
	, m_context		(DE_NULL)
	, m_emptyTarget	(0, 0, tcu::PixelFormat(0,0,0,0), 0, 0, 0)
{
	const EAGLRenderingAPI eaglApi = getEAGLApi(type);

	m_context = [[EAGLContext alloc] initWithAPI:eaglApi];
	if (!m_context)
		throw ResourceError("Failed to create EAGL context");

	try
	{
		if (![EAGLContext setCurrentContext:m_context])
			throw ResourceError("Failed to set current EAGL context");

		if (type.getAPI() == glu::ApiType::es(3,0))
			glw::initES30Direct(&m_functions);
		else if (type.getAPI() == glu::ApiType::es(2,0))
			glw::initES20Direct(&m_functions);
		else
			throw InternalError("Unsupproted API for loading functions");
	}
	catch (...)
	{
		if ([EAGLContext currentContext] == m_context)
			[EAGLContext setCurrentContext:nil];

		[m_context release];
		throw;
	}
}

RawContext::~RawContext (void)
{
	if ([EAGLContext currentContext] == m_context)
		[EAGLContext setCurrentContext:nil];

	[m_context release];
}

void RawContext::postIterate (void)
{
}

NSString* chooseLayerColorFormat (const glu::RenderConfig& config)
{
	const bool	cr		= config.redBits	!= glu::RenderConfig::DONT_CARE;
	const bool	cg		= config.greenBits	!= glu::RenderConfig::DONT_CARE;
	const bool	cb		= config.blueBits	!= glu::RenderConfig::DONT_CARE;
	const bool	ca		= config.alphaBits	!= glu::RenderConfig::DONT_CARE;

	if ((!cr || config.redBits		== 8) &&
		(!cg || config.greenBits	== 8) &&
		(!cb || config.blueBits		== 8) &&
		(!ca || config.alphaBits	== 8))
		return kEAGLColorFormatRGBA8;

	if ((!cr || config.redBits		== 5) &&
		(!cg || config.greenBits	== 6) &&
		(!cb || config.blueBits		== 5) &&
		(!ca || config.alphaBits	== 0))
		return kEAGLColorFormatRGB565;

	return nil;
}

// ScreenContext

ScreenContext::ScreenContext (ScreenManager* screenManager, const glu::RenderConfig& config)
	: RawContext			(config.type)
	, m_screenManager		(screenManager)
	, m_layer				(DE_NULL)
	, m_framebuffer			(*this) // \note Perfectly safe to give reference to this RC as everything except postIterate() works at this point.
	, m_colorBuffer			(*this)
	, m_depthStencilBuffer	(*this)
{
	m_layer = m_screenManager->acquireScreen();
	try
	{
		createFramebuffer(config);
	}
	catch (...)
	{
		m_screenManager->releaseScreen(m_layer);
		throw;
	}
}

ScreenContext::~ScreenContext (void)
{
	m_screenManager->releaseScreen(m_layer);
}

void ScreenContext::createFramebuffer (const glu::RenderConfig& config)
{
	const glw::Functions&	gl					= getFunctions();
	const NSString* const	colorFormat			= chooseLayerColorFormat(config);
	const deUint32			depthStencilFormat	= chooseDepthStencilFormat(config);
	tcu::PixelFormat		pixelFormat;
	int						width				= 0;
	int						height				= 0;
	int						depthBits			= 0;
	int						stencilBits			= 0;

	if (config.numSamples > 0)
		throw NotSupportedError("Multisample config is not supported");

	if (colorFormat == nil)
		throw NotSupportedError("Unsupported color attachment format");

	if ((config.depthBits > 0 || config.stencilBits > 0) && depthStencilFormat == 0)
		throw NotSupportedError("Unsupported depth & stencil attachment format");

	m_layer.opaque = TRUE;
	m_layer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
								  colorFormat, kEAGLDrawablePropertyColorFormat,
								  [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking,
								  nil];

	gl.bindRenderbuffer(GL_RENDERBUFFER, *m_colorBuffer);
	if (![getEAGLContext() renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)m_layer])
		throw ResourceError("Failed to allocate color renderbuffer");
	GLU_EXPECT_NO_ERROR(gl.getError(), "Creating color renderbuffer");

	gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH,		&width);
	gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT,		&height);
	gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_RED_SIZE,	&pixelFormat.redBits);
	gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_GREEN_SIZE,	&pixelFormat.greenBits);
	gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_BLUE_SIZE,	&pixelFormat.blueBits);
	gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_ALPHA_SIZE,	&pixelFormat.alphaBits);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Querying surface size failed");

	if (depthStencilFormat != 0)
	{
		gl.bindRenderbuffer(GL_RENDERBUFFER, *m_depthStencilBuffer);
		gl.renderbufferStorage(GL_RENDERBUFFER, depthStencilFormat, width, height);

		gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_DEPTH_SIZE,		&depthBits);
		gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_STENCIL_SIZE,	&stencilBits);

		GLU_EXPECT_NO_ERROR(gl.getError(), "Creating depth / stencil renderbuffer");
	}

	gl.bindFramebuffer(GL_FRAMEBUFFER, *m_framebuffer);
	gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *m_colorBuffer);

	if (depthStencilFormat != 0)
	{
		if (depthBits > 0)
			gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, *m_depthStencilBuffer);

		if (stencilBits > 0)
			gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, *m_depthStencilBuffer);
	}

	GLU_EXPECT_NO_ERROR(gl.getError(), "Creating framebuffer");

	if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
		throw NotSupportedError("Framebuffer is not complete");

	// Set up correct viewport for first test case.
	gl.viewport(0, 0, width, height);

	m_renderTarget = tcu::RenderTarget(width, height, pixelFormat, depthBits, stencilBits, 0);
}

void ScreenContext::postIterate (void)
{
	const glw::Functions& gl = getFunctions();
	gl.bindRenderbuffer(GL_RENDERBUFFER, *m_colorBuffer);

	if (![getEAGLContext() presentRenderbuffer:GL_RENDERBUFFER])
		throw ResourceError("presentRenderbuffer() failed");
}

} // ios
} // tcu
